<!DOCTYPE html>
<script src="../include.js"></script>
<script>
  function b64ToBn(b64) {
    const bin = atob(b64.replace(/-/g, '+').replace(/_/g, '/'));
    const hex = bin.split('').map((ch) => ch.charCodeAt(0).toString(16).padStart(2, '0')).join('');
    return [BigInt('0x' + hex), bin.length];
  }

  function bnToB64(bn, len) {
    const hex = bn.toString(16);
    const bin = hex.match(/.{1,2}/g).map((byte) => String.fromCharCode(parseInt(byte, 16))).join('');
    return btoa(bin.padStart(len, '\x00')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
  }

  function corruptJwk(key) {
    const [bn, len] = b64ToBn(key.d);

    // Corrupt the private key
    return {
      ...key,
      d: bnToB64(bn ^ 1n, len),
    };
  }

  function corruptPkcs8Pub(privateKey) {
    // Corrupt the public key, which is encoded between 33 and 165 for the PKCS8
    const corrupted = new Uint8Array(privateKey);
    corrupted[128] ^= 0x01;
    return corrupted.buffer;
  }

  function corruptPkcs8Priv(privateKey) {
    // Corrupt a piece of the private key, which is encoded at the end of the PKCS8
    const corrupted = new Uint8Array(privateKey);
    corrupted[corrupted.length - 20] ^= 0x01;
    return corrupted.buffer;
  }

  asyncTest(async done => {
    for (const [name, genUsages, privUsages] of [
      ["RSASSA-PKCS1-v1_5", ["sign", "verify"], ["sign"]],
      ["RSA-PSS", ["sign", "verify"], ["sign"]],
      ["RSA-OAEP", ["encrypt", "decrypt"], ["decrypt"]],
    ]) {
      for (const hash of ["SHA-1", "SHA-256", "SHA-384", "SHA-512"]) {
        const keyPair = await window.crypto.subtle.generateKey({
          name: name,
          hash: hash,
          modulusLength: 1024,
          publicExponent: new Uint8Array([1, 0, 1]),
        }, true, genUsages);

        // NOTE: We don't check the public key for RSA since it's harder to corrupt.

        for (const [format, corruptFn] of [["jwk", corruptJwk], ["pkcs8", corruptPkcs8Priv], ["pkcs8", corruptPkcs8Pub]]) {
          const privateKey = await window.crypto.subtle.exportKey(format, keyPair.privateKey);

          // Prove that the original key can be imported successfully
          await window.crypto.subtle.importKey(format, privateKey, {name: name, hash: hash,}, false, privUsages);

          // Corrupt the private key and try to import it
          const corruptedPrivateKey = corruptFn(privateKey);

          try {
            await window.crypto.subtle.importKey(format, corruptedPrivateKey, {
              name: name,
              hash: hash,
            }, false, privUsages);
            println(`${name.padEnd(5, ' ')} ${hash} PRIV - ${format.padEnd(5, ' ')} FAILED`);
          } catch (e) {
            println(`${name.padEnd(5, ' ')} ${hash} PRIV - ${format.padEnd(5, ' ')} OK: ${e}`);
          }
        }
      }
    }

    done();
  });
</script>
